/*
 * Copyright 2015-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.opentest4j;

import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * {@code FileInfo} is a pair of a file path and the contents of the file.
 *
 * <p>Its main use case is for {@code actual} and {@code expected} values in
 * {@code AssertionFailedError}.
 *
 * <p>The semantics of the {@code path} can be defined by the users of this
 * class.
 *
 * @author Marc Philipp
 * @author Reinhold Degenfellner
 * @since 1.3
 */
public class FileInfo implements Serializable {

	private static final long serialVersionUID = 1L;

	private final String path;
	private final byte[] contents;

	/**
	 * Constructs a {@code FileInfo} with a path to a file and the contents of
	 * the file.
	 *
	 * <p>Be cautious with large files (limiting the size may be a good idea).
	 *
	 * <p>The supplied byte array is not copied to avoid unnecessary memory
	 * allocations. However, callers of this constructors should not modify the
	 * byte array after calling this constructor. This behavior may change
	 * in a future release without prior notice.
	 *
	 * @param path the path to the file; must not be {@code null}
	 * @param contents the contents of the file; must not be {@code null};
	 */
	public FileInfo(String path, byte[] contents) {
		if (contents == null) {
			throw new NullPointerException("contents must not be null");
		}
		validatePath(path);
		this.contents = contents;
		this.path = path;
	}

	private static void validatePath(String path) {
		if (path == null || path.trim().isEmpty()) {
			throw new NullPointerException("path must not be null or blank");
		}
	}

	/**
	 * Returns the path to the file.
	 *
	 * @return the path to the file; never {@code null}
	 */
	public String getPath() {
		return path;
	}

	/**
	 * Returns the contents of the file.
	 *
	 * <p>This method does currently not return a defensive copy of the
	 * contained byte array to avoid unnecessary memory allocations. However,
	 * callers of this method should not modify the returned byte array. This
	 * behavior may change in a future release without prior notice.
	 *
	 * @return the contents of the file; never {@code null}
	 */
	public byte[] getContents() {
		return contents;
	}

	/**
	 * Returns the contents of the file as a {@code String}.
	 *
	 * @return the contents of the file as a {@code String}; never {@code null}
	 */
	public String getContentsAsString(Charset charset) {
		return new String(contents, charset);
	}

	/**
	 * Determines if the supplied object is an instance of {@code FileInfo}
	 * with the same path and contents as this {@code FileInfo}.
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null || getClass() != obj.getClass()) {
			return false;
		}
		FileInfo that = (FileInfo) obj;
		return this.path.equals(that.path) && Arrays.equals(this.contents, that.contents);
	}

	/**
	 * Generates a unique hash code for this {@code FileInfo} based
	 * on its {@link #getPath() path}.
	 */
	@Override
	public int hashCode() {
		return path.hashCode();
	}

	/**
	 * Generates a string representation of this {@code FileInfo} based
	 * on the {@link #getPath() path} and {@link #getContents() contents}.
	 */
	@Override
	public String toString() {
		return "FileInfo[path='" + this.path + "', contents containing " + this.contents.length + " bytes]";
	}
}
